--- init_by_lua 阶段加载执行
--- 所有全局变量以 g_ 开头,请确保全局变量在除 本文件以外的其他代码中"只用不改"
--- 所有全局函数以 gfn_ 开头

local config = require("config")
local util = require("lib.util")
local rule_lib = require("lib.rule_core.rule")
local host_lib = require("lib.rule_core.host")
local post_lib = require("lib.post")
local string_util = require("lib.string_util")
local moses = require("lib.packages.moses_min")


-- 各个检测阶段的检测开关状态
local switch_on = {
    ip_white = util.check_switch_is_on(config.ip_white_check),
    ip_black = util.check_switch_is_on(config.ip_black_check),
    ua_white = util.check_switch_is_on(config.user_agent_white_check),
    ua = util.check_switch_is_on(config.user_agent_check),
    url = util.check_switch_is_on(config.url_check),
    url_white = util.check_switch_is_on(config.url_white_check),
    seo_spider = util.check_switch_is_on(config.seo_spider_check),
    ip_foreign = util.check_switch_is_on(config.ip_foreign_check),
    cc = util.check_switch_is_on(config.cc_check),
    cookie = util.check_switch_is_on(config.cookie_check),
    args = util.check_switch_is_on(config.args_check),
    post = util.check_switch_is_on(config.post_check),
    header = util.check_switch_is_on(config.header_check),
}

-- waf日志文件handle 全局变量
g_log_file_handle = io.open(config.waf_log_file, "a+")
if not g_log_file_handle then
    ngx.log(ngx.ERR, "[easy_waf] easy_ngx_waf log file open failed at init phase.")
end

-- waf总开关 全局变量
g_waf_enable = util.check_switch_is_on(config.waf_enable)


-- 读取加载所有WAF规则到 全局变量
g_rules = rule_lib.load_all_rules(true)

-- 读取加载所有蜘蛛规则数据到 全局变量
g_spider_data = rule_lib.load_spider_data()


---将规则value转化为字符串,以便日志记录(有些规则value是数组)
---@param rule_value any
---@return string
local function rule_val_to_str(rule_value)
    if type(rule_value) == "table" then
        return "[" .. table.concat(rule_value, ",") .. "]"
    end
    return "(" .. tostring(rule_value) .. ")"
end

---执行规则校验;
---     校验 g_rules[rule_key][rule_act] 规则列表中每一条规则,
---         命中任何一条即刻返回true;
---         没有对应规则列表或者所有规则都没命中返回false;
---@param rule_key string 规则主键
---@param rule_act string 规则act "deny"/"allow"/"next"
---@param var_value any 具体要校验的客户端来访值(根据规则变量var获取到的)
---@return boolean
local function do_check(rule_key, rule_act, var_value)
    if g_rules[rule_key] and g_rules[rule_key][rule_act] then
        if rule_act == "next" then
            for _, rule in ipairs(g_rules[rule_key][rule_act]) do
                -- act:next 只有在 rule["log"]="on" 时才有意义,再运算执行
                if util.check_switch_is_on(rule["log"]) then
                    local var_v
                    local msg_var = rule["var"]
                    if rule["var_s"] and var_value[rule["var_s"]] then
                        var_v = var_value[rule["var_s"]]
                        msg_var = rule["var"] .. ":" .. rule["var_s"]
                    elseif type(var_value) == "table" then
                        var_v = ""
                        for _, v in pairs(var_value) do
                            var_v = var_v .. " " .. v
                        end
                    else
                        var_v = var_value
                    end
                    local res, err_info = rule_lib.execute(var_v, rule["op"], rule["value"])
                    if res then
                        util.waf_log("[rule:" .. rule_key .. ":" .. rule_act .. "] ezv_"
                            .. rule_val_to_str(var_v) .. "_ezv var_"
                            .. rule_val_to_str(msg_var) .. "_var rule_"
                            .. rule_val_to_str(rule["value"]) .. "_rule "
                            .. (rule["msg"] or "")
                        )
                        return true
                    elseif res == nil then
                        ngx.log(ngx.ERR,
                                "[easy_waf] " ..
                                rule_key .. ":" .. rule_act .. " execute(nil) reason:" .. tostring(err_info))
                    end
                end
            end
        else
            -- act:allow/deny
            for _, rule in ipairs(g_rules[rule_key][rule_act]) do
                local var_v
                local msg_var = rule["var"]
                if rule["var_s"] and var_value[rule["var_s"]] then
                    var_v = var_value[rule["var_s"]]
                    msg_var = rule["var"] .. ":" .. rule["var_s"]
                elseif type(var_value) == "table" then
                    var_v = ""
                    for _, v in pairs(var_value) do
                        var_v = var_v .. " " .. v
                    end
                else
                    var_v = var_value
                end
                local res, err_info = rule_lib.execute(var_v, rule["op"], rule["value"])
                if res then
                    if util.check_switch_is_on(rule["log"]) then
                        util.waf_log("[rule:" .. rule_key .. ":" .. rule_act .. "] ezv_"
                            .. rule_val_to_str(var_v) .. "_ezv var_"
                            .. rule_val_to_str(msg_var) .. "_var rule_"
                            .. rule_val_to_str(rule["value"]) .. "_rule "
                            .. (rule["msg"] or "")
                        )
                    end
                    return true
                elseif res == nil then
                    ngx.log(ngx.ERR,
                            "[easy_waf] " .. rule_key .. ":" .. rule_act .. " execute(nil) reason:" .. tostring(err_info))
                end
            end
        end
    end
    return false
end


-- IP CIDR工具类
g_ipmatcher = nil
if (require("ffi")).os == "Windows" then
    g_ipmatcher = require("lib.packages.ipmatcher_win")
else
    g_ipmatcher = require("lib.packages.ipmatcher")
end

---来访IP扫描(没有act),只针对log:on的规则进行日志记录
---@param client_ip string 来访IP
gfn_ip_next_check = function(client_ip)
    do_check("ip", "next", client_ip)
end

---来访IP在白名单内返回true, 未开启白名单检测功能/来访IP不在名单内,返回false
---@param client_ip string 来访IP
---@param host string 被访主机
---@return boolean
gfn_ip_white_check = function(client_ip, host)
    if switch_on.ip_white
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_IP_WHITE))
    then
        local res = do_check("ip", "allow", client_ip)
        if res then
            return res
        end
    end
    return false
end


---来访IP在黑名单内返回true, 未开启黑名单检测功能/来访IP不在名单内,返回false
---@param client_ip string 来访IP
---@param host string 被访主机
---@return boolean
gfn_ip_black_check = function(client_ip, host)
    if switch_on.ip_black
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_IP_BLACK))
    then
        local res = do_check("ip", "deny", client_ip)
        if res then
            return res
        end
    end
    return false
end


---来访UA+IP在蜘蛛规则名单内返回true, 未开启UA蜘蛛检测功能/来访UA+IP不在蜘蛛规则名单内,返回false
---@param ua string user-agent
---@param client_ip string 来访IP
---@return boolean
gfn_seo_spider_check = function(ua, client_ip)
    if switch_on.seo_spider then
        local is_seo, seo_name = rule_lib.check_is_seo(ua, client_ip)
        if is_seo and util.check_switch_is_on(config.seo_spider_log) then
            util.waf_log("[rule:seo_spider] (" .. (seo_name or "unknown") .. ")")
        end
        return is_seo
    end
    return false
end


-- 加载libmaxminddb以及Country ip库
-- 全局变量 g_maxminddb
g_maxminddb = require("lib.packages.maxminddb")
if not g_maxminddb.initted() then
    g_maxminddb.init(config.waf_base_path .. "/data/Country.mmdb")
end


---来访IP是境外IP返回true, 未开启境外IP检测功能/来访IP非境外IP,返回false
---@param client_ip string 来访IP
---@param host string 被访主机
---@return boolean
gfn_ip_foreign_check = function(client_ip, host)
    if switch_on.ip_foreign
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_IP_FOREIGN))
    then
        -- support ipv6 e.g. 2001:4860:0:1001::3004:ef68
        local res, err = g_maxminddb.lookup(client_ip)
        if not res then
            ngx.log(ngx.ERR, "[easy_waf] failed to LOOKUP ip:(" .. client_ip .. ") reason:" .. tostring(err))
            return false
        end
        -- res -> table  {"country":{"iso_code":"CN"}}
        if res.country and res.country.iso_code then
            local country_code = res.country.iso_code
            for _, v in ipairs(config.domestic_iso_code) do
                if country_code == v then
                    -- 在 非境外地区列表中
                    return false
                end
            end
            -- 不在 非境外地区列表中
            if util.check_switch_is_on(config.ip_foreign_log) then
                util.waf_log("[rule:ip_foreign] (" .. country_code .. ")")
            end
            return true
        end
    end
    return false
end


---来访UA在白名单内返回true, 未开启UA白名单检测功能/来访UA不在白名单内,返回false
---@param ua string 来访user-agent
---@param host string 被访主机
---@return boolean
gfn_user_agent_white_check = function(ua, host)
    if switch_on.ua_white
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_UA))
    then
        local res = do_check("ua", "allow", ua)
        if res then
            return res
        end
    end
    return false
end


---来访UA在规则名单内返回true, 未开启UA检测功能/来访UA不在规则名单内,返回false
---@param ua string 来访user-agent
---@param host string 被访主机
---@return boolean
gfn_user_agent_check = function(ua, host)
    if switch_on.ua
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_UA))
    then
        -- 针对next的校验匹配,不用关心结果
        do_check("ua", "next", ua)

        local res = do_check("ua", "deny", ua)
        if res then
            return res
        end
    end
    return false
end

---URL在规则白名单内返回true, 未开启URL白名单检测功能/来访URL不在规则名单内,返回false;
---@param request_uri string 请求URI
---@param request_filename string 请求的具体文件
---@return boolean
gfn_url_white_check = function(request_uri, request_filename)
    if switch_on.url_white then
        -- URL检测阶段需要检测的规则主键有:
        --      "request_uri","request_uri_raw","request_filename"

        local res = do_check("request_uri", "allow", request_uri)
        if res then
            return res
        end

        -- 白名单太敏感，不做以下两个规则分类的 allow 校验
        -- "request_uri_raw", "request_filename"
    end
    return false
end


---URL在规则内返回true, 未开启URL检测功能/来访URL不在规则名单内,返回false
---@param request_uri string 请求URI
---@param request_filename string 请求的具体文件
---@return boolean
gfn_url_check = function(request_uri, request_filename)
    if switch_on.url then
        -- URL检测阶段需要检测的规则主键有:
        --      "request_uri","request_uri_raw","request_filename"

        -- 针对next的校验匹配,不用关心结果
        do_check("request_uri", "next", request_uri)
        do_check("request_uri_raw", "next", ngx.unescape_uri(request_uri))
        do_check("request_filename", "next", ngx.unescape_uri(request_filename))

        local res = do_check("request_uri", "deny", request_uri)
        if res then
            return res
        end

        res = do_check("request_uri_raw", "deny", ngx.unescape_uri(request_uri))
        if res then
            return res
        end

        res = do_check("request_filename", "deny", ngx.unescape_uri(request_filename))
        if res then
            return res
        end
    end
    return false
end


-- 全局变量
g_cc_size = tonumber(string.match(config.cc_rate, "(.*)/"))
g_cc_window = tonumber(string.match(config.cc_rate, "/(.*)"))
g_cckey_prefix = "ez:cckey:"
g_ccip_prefix = "ez:ccip:"
local ccutil = require("lib.ccutil")
ccutil.init()
---CC检测
---@param ip string 来访IP
---@param uri string 请求URI(不含参数)
---@param host string 被访主机
---@return boolean
gfn_cc_check = function(ip, uri, host)
    if switch_on.cc
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_CC))
    then
        if ccutil.ccip_exist(ip) then
            return true
        else
            -- 防止uri过长在存储时出现意外情况,在此md5
            local token = g_cckey_prefix .. ip .. ":" .. ngx.md5(uri)
            local res = ccutil.get(token)
            if res ~= nil then
                local token_count = tonumber(res)
                if token_count >= g_cc_size then
                    ccutil.set_ccip(ip, config.cc_ip_lock_time)
                    util.waf_log("[rule:cc_deny]")
                    return true
                else
                    ccutil.incr(token)
                end
            else
                ccutil.set(token, 1, g_cc_window)
            end
        end
    end
    return false
end


---cookie在规则内返回true, 未开启cookie检测功能/来访cookie不在规则名单内,返回false
---@param host string 被访主机
---@return boolean
gfn_cookie_check = function(host)
    if switch_on.cookie
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_COOKIE))
    then
        -- cookie检测阶段需要检测的规则主键有:
        --      "cookie","request_cookies","request_cookies_names"

        local cookie_str = ngx.var.http_cookie
        if not cookie_str then
            return false
        end

        local cookie_tbl = util.cookie_string_to_table(cookie_str)

        for _, cookie in ipairs(cookie_tbl) do
            -- 针对next的校验匹配,不用关心结果
            do_check("cookie", "next", cookie["v"])
            do_check("request_cookies", "next", cookie["v"])
            do_check("request_cookies_names", "next", cookie["k"])
        end

        local res
        for _, cookie in ipairs(cookie_tbl) do
            res = do_check("cookie", "deny", cookie["v"])
            if res then
                return res
            end

            res = do_check("request_cookies", "deny", cookie["v"])
            if res then
                return res
            end

            res = do_check("request_cookies_names", "deny", cookie["k"])
            if res then
                return res
            end
        end
    end
    return false
end


---args在规则内返回true, 未开启args检测功能/args不在规则名单内,返回false;
---校验所有 args_name 以及 args 参数值; 忽略所有act:allow的规则;
---@param host string 被访主机
---@return boolean
gfn_args_check = function(host)
    if switch_on.args
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_ARGS))
    then
        -- args检测阶段需要检测的规则主键有:
        --      "query_string","args_get","args_get_names"

        if g_rules["query_string"] then
            -- query_string 是原始提供的，不进行URL解码
            local query_string = ngx.var.query_string
            if query_string then
                local s = ngx.unescape_uri(query_string)
                -- 针对next的校验匹配,不用关心结果
                do_check("query_string", "next", s)
                local res = do_check("query_string", "deny", s)
                if res then
                    return res
                end
            end
        end

        local args = ngx.req.get_uri_args()

        -- 针对args act:next相关的规则应该是极少数情况，在此先作判断再循环
        if g_rules["args_get"]["next"]
            or g_rules["args_get_names"]["next"]
        then
            for args_name, args_value in pairs(args) do
                -- 针对 args_value 参数值进行处理先
                local args_data = util.to_string(args_value, " ")

                -- 针对next的校验匹配,不用关心结果
                do_check("args_get", "next", ngx.unescape_uri(args_data))
                do_check("args_get_names", "next", ngx.unescape_uri(args_name))
            end
        end

        for args_name, args_value in pairs(args) do
            local res = do_check("args_get_names", "deny", ngx.unescape_uri(args_name))
            if res then
                return res
            end

            -- 针对 args_value 参数值进行处理先
            local args_data = util.to_string(args_value, " ")

            res = do_check("args_get", "deny", ngx.unescape_uri(args_data))
            if res then
                return res
            end
        end -- END args 循环
    end
    return false
end


---post在规则内返回true, 未开启post检测功能/post不在规则名单内,返回false;
---校验所有 post_name 以及 post 参数值; 忽略所有act:allow的规则;
---@param host string 被访主机
---@return boolean
gfn_post_check = function(host)
    if (ngx.req.get_method() == "POST")
        and switch_on.post
        and (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_POST))
    then
        -- post参数检测阶段需要检测的规则主键有:
        --      "args_post","args_post_names" 所有POST都检测
        --      "request_body" 只有 application/x-www-form-urlencoded
        --      "files","files_names" 只有 multipart/form-data

        local content_type = ngx.var.content_type
        if not content_type then
            return false
        end

        local ct = post_lib.parse_content_type(content_type)

        if ct == post_lib.CONTENT_TYPE_WWW_FORM_URLENCODED then
            -- Content-Type: application/x-www-form-urlencoded

            ngx.req.read_body()
            local post_args = ngx.req.get_post_args()
            if not post_args then
                return false
            end

            -- 针对 act:next 相关的规则应该是极少数情况，在此先作判断再循环
            if g_rules["args_post_names"]["next"] then
                -- 针对 args_post_names 参数名的校验
                for post_name, _ in pairs(post_args) do
                    do_check("args_post_names", "next", ngx.unescape_uri(post_name))
                end
            end

            if g_rules["args_post"]["next"] then
                -- 针对 args_post 参数值的校验
                for _, post_value in pairs(post_args) do
                    local post_data = util.to_string(post_value, " ")

                    -- 针对next的校验匹配,不用关心结果
                    do_check("args_post", "next", ngx.unescape_uri(post_data))
                end
            end

            -- 针对 act:deny 的校验
            if g_rules["args_post_names"]["deny"] or g_rules["args_post"]["deny"] then
                for post_name, post_value in pairs(post_args) do
                    local res = do_check("args_post_names", "deny", ngx.unescape_uri(post_name))
                    if res then
                        return res
                    end

                    -- 针对 args_post 参数值的校验
                    local post_data = util.to_string(post_value, " ")

                    res = do_check("args_post", "deny", ngx.unescape_uri(post_data))
                    if res then
                        return res
                    end
                end
            end

            -- "request_body" 包含原始请求体 当检测到 "application/x-www-form-urlencoded" 内容类型时
            if g_rules["request_body"]
                and (
                    g_rules["request_body"]["next"]
                    or g_rules["request_body"]["deny"]
                )
            then
                local request_body = ngx.var.request_body
                if request_body then
                    do_check("request_body", "next", ngx.unescape_uri(request_body))

                    local res = do_check("request_body", "deny", ngx.unescape_uri(request_body))
                    if res then
                        return res
                    end
                end
            end
        elseif ct == post_lib.CONTENT_TYPE_FORM_DATA then
            -- Content-Type: multipart/form-data

            ngx.req.read_body()
            local content_len = tonumber(ngx.var.content_length)
            if (not content_len) or (content_len < 1) then
                return false
            end
            -- 解析出 boundary 字符串
            local boundary = post_lib.parse_form_data_boundary(content_type)
            if not boundary then
                return false
            end

            local body_data = ngx.req.get_body_data()

            local form_data = {
                file = {},
                field = {},
            }
            local tmp_field_data = {}
            if body_data then
                local is_file = false
                local is_field = false
                local has_split_empty = false
                for line in string.gmatch(body_data, ".-\r?\n") do
                    -- 一行一行处理
                    local line_str = string_util.trim(line)
                    local is_boundary_line = post_lib.is_form_data_boundary_line(boundary, line_str)
                    if is_boundary_line then
                        if is_file then
                            table.insert(form_data["file"], moses.clone(tmp_field_data))
                        elseif is_field then
                            table.insert(form_data["field"], moses.clone(tmp_field_data))
                        end
                        -- 重置
                        tmp_field_data = {}
                        is_file = false
                        is_field = false
                        has_split_empty = false
                    elseif not is_file then
                        -- 文件内容此处未考虑验证,在已经确认是上传文件的情况下不处理行内容

                        local line_type, info = post_lib.parse_form_data_line(boundary, line_str)
                        if line_type == post_lib.FORM_DATA_LINE_TYPE_FILE and info then
                            tmp_field_data = {
                                filename = (info["filename"] or ""),
                                name = (info["name"] or ""),
                            }
                            is_file = true
                        elseif line_type == post_lib.FORM_DATA_LINE_TYPE_FIELD and info then
                            tmp_field_data = {
                                name = (info["name"] or ""),
                                value = {}
                            }
                            is_field = true
                        elseif line_type == post_lib.FORM_DATA_LINE_EMPTY then
                            has_split_empty = true
                        elseif is_field and has_split_empty then
                            if tmp_field_data["value"] then
                                table.insert(tmp_field_data["value"], line_str)
                            else
                                tmp_field_data["value"] = { line_str }
                            end
                        end
                    end
                end
            else
                -- 通过临时文件获取body_data
                local body_file = ngx.req.get_body_file()
                if not body_file then
                    return false
                end
                local fh, err = io.open(body_file, "r")
                if fh then
                    local is_file = false
                    local is_field = false
                    local has_split_empty = false
                    fh:seek("set")
                    local line = fh:read("*l")
                    while line do
                        -- 一行一行处理
                        local is_boundary_line = post_lib.is_form_data_boundary_line(boundary, line)
                        if is_boundary_line then
                            if is_file then
                                table.insert(form_data["file"], moses.clone(tmp_field_data))
                            elseif is_field then
                                table.insert(form_data["field"], moses.clone(tmp_field_data))
                            end
                            -- 重置
                            tmp_field_data = {}
                            is_file = false
                            is_field = false
                            has_split_empty = false
                        elseif not is_file then
                            -- 文件内容此处未考虑验证,在已经确认是上传文件的情况下不处理行内容

                            local line_type, info = post_lib.parse_form_data_line(boundary, line)
                            if line_type == post_lib.FORM_DATA_LINE_TYPE_FILE and info then
                                tmp_field_data = {
                                    filename = (info["filename"] or ""),
                                    name = (info["name"] or ""),
                                }
                                is_file = true
                            elseif line_type == post_lib.FORM_DATA_LINE_TYPE_FIELD and info then
                                tmp_field_data = {
                                    name = (info["name"] or ""),
                                    value = {}
                                }
                                is_field = true
                            elseif line_type == post_lib.FORM_DATA_LINE_EMPTY then
                                has_split_empty = true
                            elseif is_field and has_split_empty then
                                if tmp_field_data["value"] then
                                    table.insert(tmp_field_data["value"], line)
                                else
                                    tmp_field_data["value"] = { line }
                                end
                            end
                        end

                        line = fh:read("*l")
                    end

                    fh:close()
                else
                    ngx.log(ngx.ERR, "[easy_waf] get_body_file() open failed:" .. tostring(err))
                    return false
                end
            end

            -- 针对 act:next 相关的规则应该是极少数情况，在此先作判断再循环
            if g_rules["args_post_names"]["next"] or g_rules["args_post"]["next"] then
                for _, item in ipairs(form_data["field"]) do
                    do_check("args_post_names", "next", ngx.unescape_uri(item["name"]))
                    do_check("args_post", "next", ngx.unescape_uri(table.concat(item["value"], " ")))
                end
            end

            -- act:deny
            for _, item in ipairs(form_data["field"]) do
                local res =
                    do_check("args_post_names",
                             "deny",
                             ngx.unescape_uri(item["name"])
                    )
                if res then
                    return res
                end

                res =
                    do_check("args_post",
                             "deny",
                             ngx.unescape_uri(table.concat(item["value"], " "))
                    )
                if res then
                    return res
                end
            end

            -- files:上传文件原始名  files_names:上传文件表单名
            if g_rules["files"]["next"] or g_rules["files_names"]["next"] then
                for _, item in ipairs(form_data["file"]) do
                    do_check("files", "next", ngx.unescape_uri(item["filename"]))
                    do_check("files_names", "next", ngx.unescape_uri(item["name"]))
                end
            end

            if g_rules["files"]["deny"] or g_rules["files_names"]["deny"] then
                for _, item in ipairs(form_data["file"]) do
                    local res = do_check("files", "deny", ngx.unescape_uri(item["filename"]))
                    if res then
                        return res
                    end

                    res = do_check("files_names", "deny", ngx.unescape_uri(item["name"]))
                    if res then
                        return res
                    end
                end
            end
        elseif ct == post_lib.CONTENT_TYPE_JSON then
            -- Content-Type: application/json
            ngx.req.read_body()
            local cjson = require("cjson")
            local body_data = ngx.req.get_body_data()
            if not body_data then
                return false
            end
            local success, result = pcall(cjson.decode, body_data)
            if not success then
                return false
            end
            local res_type = type(result)
            if res_type == "table" then
                -- 针对 act:next 相关的规则应该是极少数情况，在此先作判断再循环
                if g_rules["args_post_names"]["next"] or g_rules["args_post"]["next"] then
                    -- 针对 args_post_names args_post 的校验
                    for post_name, post_value in pairs(result) do
                        do_check("args_post_names", "next", ngx.unescape_uri(post_name))
                        local post_data = util.to_string(post_value, " ")
                        do_check("args_post", "next", ngx.unescape_uri(post_data))
                    end
                end

                -- 针对 act:deny 的校验
                if g_rules["args_post_names"]["deny"] or g_rules["args_post"]["deny"] then
                    for post_name, post_value in pairs(result) do
                        local res = do_check("args_post_names", "deny", ngx.unescape_uri(post_name))
                        if res then
                            return res
                        end

                        -- 针对 args_post 参数值的校验
                        local post_data = util.to_string(post_value, " ")

                        res = do_check("args_post", "deny", ngx.unescape_uri(post_data))
                        if res then
                            return res
                        end
                    end
                end
            elseif res_type == "string" then
                do_check("args_post", "next", ngx.unescape_uri(result))
                local res = do_check("args_post", "deny", ngx.unescape_uri(result))
                if res then
                    return res
                end
            end
        else
            -- Content-Type: 其他类型
            ngx.req.read_body()
            local body_data = ngx.req.get_body_data()
            if not body_data then
                return false
            end

            -- 针对 act:deny 的校验
            local res = do_check("args_post_names", "deny", ngx.unescape_uri(body_data))
            if res then
                return res
            end

            res = do_check("args_post", "deny", ngx.unescape_uri(body_data))
            if res then
                return res
            end
        end
    end
    return false
end


---header规则校验(不含user-agent cookie)
---     校验命中返回true, 否则返回false;
---     在规则内返回true, 未开启header检测功能/header不在规则名单内,返回false;
---忽略所有act:allow的规则;
---@param host string 被访主机
---@return boolean
gfn_header_check = function(host)
    if (switch_on.header and
            (not host_lib.is_ex_host(host, host_lib.XHOST_SCOPE_HEADER))
        )
    then
        -- header检测阶段需要检测的规则主键有:
        --      "request_headers_names","request_headers"

        if g_rules["request_headers_names"]["next"]
            or g_rules["request_headers"]["next"]
            or g_rules["request_headers_names"]["deny"]
            or g_rules["request_headers"]["deny"]
        then
            local headers = ngx.req.get_headers(100)
            if headers["user_agent"] then
                headers["user_agent"] = nil
            end
            if headers["cookie"] then
                headers["cookie"] = nil
            end

            local headers_names = {}

            for k, _ in pairs(headers) do
                table.insert(headers_names, k)
            end

            do_check("request_headers_names", "next", headers_names)
            do_check("request_headers", "next", headers)

            local res = do_check("request_headers_names", "deny", headers_names)
            if res then
                return res
            end

            res = do_check("request_headers", "deny", headers)
            if res then
                return res
            end
        end
    end
    return false
end


---拦截响应
---@param resp number | string 拦截响应状态码/响应html
gfn_deny_response = function(resp)
    local return_type = type(resp)
    if return_type == "number" then
        ngx.status = resp
        return ngx.exit(resp)
    elseif resp == "default" then
        -- 状态码设置要先于 say() 否则会有 error log
        ngx.status = config.default_return_code
        ngx.say(config.default_return_html)
        return ngx.exit(ngx.HTTP_OK)
    else
        ngx.status = config.default_return_code
        ngx.say(resp)
        return ngx.exit(ngx.HTTP_OK)
    end
end
